package org.budo.graph.service.util;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.budo.support.javax.sql.util.ResultSetUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.context.ApplicationContext;

import com.alibaba.druid.pool.DruidDataSource;

import io.shardingsphere.core.jdbc.core.datasource.MasterSlaveDataSource;

/**
 * @author lmw
 */
public class DatabaseMetadataUtil {
    private static final Logger log = Slf4j.getLogger();

    private static final Boolean HAS_SHARDING = ReflectUtil.hasClass("io.shardingsphere.core.jdbc.core.datasource.MasterSlaveDataSource");

    public static String writeDatabaseMetadataResponse(ApplicationContext applicationContext, String url) {
        String[] beanNames = applicationContext.getBeanNamesForType(DataSource.class);
        if (null == beanNames || beanNames.length < 1) {
            log.error("#30 beanNames for DataSource.class is null or empty, beanNames=" + StringUtil.join(beanNames) + ", applicationContext=" + applicationContext);
            return "NoDataSource";
        }

        String result = "<pre>";
        for (String beanName : beanNames) {
            DataSource dataSource = (DataSource) applicationContext.getBean(beanName);
            result += "\n" + dataSourceToString(dataSource) + "\n";
            result += getDatabaseMetaData(dataSource);
            result += "\n";
        }
        result += "</pre>";
        return result;

    }

    private static String dataSourceToString(DataSource dataSource) {
        if (null == dataSource) {
            return null;
        }

        if (dataSource instanceof DruidDataSource) {
            return "Druid@" + Integer.toHexString(dataSource.hashCode()) + " " + ((DruidDataSource) dataSource).getUrl();
        }

        return dataSource.getClass().getName() + "@" + Integer.toHexString(dataSource.hashCode());
    }

    public static String getDatabaseMetaData(DataSource dataSource) {
        if (null == dataSource) {
            log.error("getDatabaseMetaData, dataSource is null, dataSource=" + dataSource);
            return "dataSource is null";
        }

        if (HAS_SHARDING && dataSource instanceof MasterSlaveDataSource) {
            return "MasterSlaveDataSource";
        }

        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            return doGetDatabaseMetaData(connection);

        } catch (Throwable e) {
            throw new RuntimeException("#78 dataSource=" + dataSource, e);
        } finally {
            if (null != connection) {
                try {
                    connection.close();
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private static String doGetDatabaseMetaData(Connection connection) throws Throwable {
        Statement statement = connection.createStatement();
        DatabaseMetaData metaData = connection.getMetaData();

        ResultSet tableResultSet = metaData.getTables(null, null, null, null);
        List<Map<String, Object>> tableMapList = ResultSetUtil.toMapList(tableResultSet);

        List<String> tableMetaDatas = new ArrayList<String>();
        for (Map<String, Object> tableMap : tableMapList) {
            String tableName = (String) tableMap.get("TABLE_NAME");

            ResultSet columnResultSet = statement.executeQuery("DESC `" + tableName + "`");
            List<Map<String, Object>> columnMapList = ResultSetUtil.toMapList(columnResultSet);

            List<String> tablePrimaryKeys = new ArrayList<String>();

            List<String> columnMetaDatas = new ArrayList<String>();
            for (Map<String, Object> columnMap : columnMapList) {
                String columnMetaData = "";//

                Object columnName = columnMap.get("Field");
                columnMetaData += "`" + columnName + "`";//

                String columnType = sortEnumValues((String) columnMap.get("Type"));
                columnMetaData += " " + columnType; //

                Object isNullable = columnMap.get("Null");
                if (StringUtil.equalsIgnoreCase("NO", isNullable + "")) {
                    columnMetaData += " NOT NULL";
                }

                Object isAutoincrement = columnMap.get("IS_AUTOINCREMENT");
                if (StringUtil.equalsIgnoreCase("YES", isAutoincrement + "")) {
                    columnMetaData += " AUTO_INCREMENT";
                }

                Object extra = columnMap.get("Extra");
                if (null != extra && !StringUtil.isEmpty(extra + "")) {
                    columnMetaData += " " + extra;
                }

                Object columnDefault = columnMap.get("Default");
                if (null != columnDefault) {
                    if ("bit(1)".equalsIgnoreCase(columnType) // 特定情况的SQL默认值不加引号
                            || "CURRENT_TIMESTAMP".equals(columnDefault)) { // 默认值不加引号
                        columnMetaData += " DEFAULT " + columnDefault;
                    } else { // 加引号
                        columnMetaData += " DEFAULT '" + columnDefault + "'";
                    }
                }

                if ("PRI".equals(columnMap.get("Key"))) {
                    tablePrimaryKeys.add((String) columnName);
                }

                columnMetaDatas.add(columnMetaData);
            }

            // 对所有列排序
            Collections.sort(columnMetaDatas);

            String tableMetaData = "TABLE `" + tableName + "` ( \n\t";//
            tableMetaData += StringUtil.join(columnMetaDatas, ", \n\t");

            // 支持复合主键 PRIMARY KEY (`project_id`,`engineer_id`),
            if (!tablePrimaryKeys.isEmpty()) {
                tableMetaData += ",\n\tPRIMARY KEY (" + StringUtil.join(tablePrimaryKeys, ",", "`", "`") + ")";
            }

            tableMetaData += "\n); ";
            tableMetaDatas.add(tableMetaData);
        }

        // 对所有表排序
        Collections.sort(tableMetaDatas);

        statement.close();
        return StringUtil.join(tableMetaDatas, "\n");
    }

    /**
     * 枚举类型排序
     */
    private static String sortEnumValues(String typeString) {
        if (!StringUtil.startsWith(typeString, "enum('")) {
            return typeString;
        } else {
            String valueString = typeString.replace("enum(", "").replace(")", "");
            String[] values = valueString.split(",");

            Arrays.sort(values);

            return "enum(" + StringUtil.join(values, ",\n\t\t") + ")";
        }
    }
}